package com.halden.mycache.core.Impl;

import com.halden.mycache.support.EvictStrategy.EvictStrategy;
import com.halden.mycache.support.EvictStrategy.impl.CacheEvictContext;
import com.halden.mycache.support.saveStrategy.AOFSaveStrategy.IAOFSaveStrategy;
import com.halden.mycache.support.saveStrategy.RDBSaveStrategy.IRDBSaveStrategy;
import com.halden.mycache.core.IMyCache;
import com.halden.mycache.support.aop.CacheInterceptor;
import com.halden.mycache.support.listener.CacheRemoveContext;
import com.halden.mycache.support.listener.ICacheRemoveListener;
import com.halden.mycache.support.listener.RemoveType;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.io.*;
import java.util.Date;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * @since 0.0.1
 * @param <K>
 * @param <V>
 */
@Data
@Slf4j
@Component(value = "myCache")
public class MyCache<K,V> implements IMyCache<K,V>{

    /**
     * 组合一个map，用于完成map已有的功能
     * @since 0.0.1
     */
    private Map<K,CacheValue<V>> map;

    /**
     * 选用的内存淘汰策略
     * @since 0.0.1
     */
    private EvictStrategy evictStrategy;

    /**
     * 指定的缓存大小
     * @since 0.0.1
     */
    private int size;

    /**
     * 实际存储的数据大小
     * @since 0.0.1
     */
    private int length;

    /**
     * rdb持久化策略
     * @since 0.0.3
     */
    private IRDBSaveStrategy<K,V> rdbSaveStrategy;

    /**
     * rdb持久化文件名
     * @since 0.0.3
     */
    private String rdbFileName;

    /**
     * 删除监听器
     */
    private ICacheRemoveListener<K,V> cacheRemoveListener;

    /**
     * aof持久化策略，在主对象中用于关闭文件流和读取aof存储文件
     */
    private IAOFSaveStrategy aofSaveStrategy;

    @Override
    public boolean isEmpty() {
        return length == 0;
    }

    @Override
    @CacheInterceptor(aof = false)
    public boolean containsKey(K key) {
        return this.map.containsKey(key);
    }

    @Override
    @CacheInterceptor(aof = false)
    public V get(K key){
        CacheValue<V> cacheValue = this.simpleGet(key);
        if (Objects.isNull(cacheValue)){
            return null;
        }
        // 如果过期了
        if (!this.checkExpire(key, cacheValue)){
            return null;
        }
        cacheValue = evictStrategy.visit(new CacheEvictContext(cacheValue));
        return cacheValue.getValue();
    }

    public CacheValue<V> getCacheValue(K key){
        CacheValue<V> cacheValue = this.simpleGet(key);
        if (Objects.isNull(cacheValue)){
            return null;
        }
        cacheValue = evictStrategy.visit(new CacheEvictContext(cacheValue));
        return cacheValue;
    }

    @Override
    @CacheInterceptor(aof = false)
    public CacheValue<V> simpleGet(K key){
        return map.get(key);
    }

    @Override
    @CacheInterceptor
    public CacheValue<V> put(K key, V value) {
        // 先看是不是修改
        CacheValue<V> cacheValue = getCacheValue(key);
        if (cacheValue != null){
            cacheValue.setValue(value);
            return cacheValue;
        }
        // 内存已满，进行淘汰
        if (length == size){
            CacheEvictContext context = new CacheEvictContext(this);
            evictStrategy.evict(context);
        }
        // 添加
        cacheValue = new CacheValue<>(value);
        map.put(key, cacheValue);
        length++;
        return cacheValue;
    }

    @Override
    @CacheInterceptor
    public V remove(K key, CacheRemoveContext<K,V> context) {
        this.length--;
        cacheRemoveListener.listen(context);
        return map.remove(key).getValue();
    }

    @Override
    @CacheInterceptor
    public V remove(K key){
        this.length--;
        cacheRemoveListener.listen(new CacheRemoveContext<>(key, simpleGet(key).getValue(), RemoveType.USER_REMOVE));
        return map.remove(key).getValue();
    }

    @Override
    @CacheInterceptor
    public void clear() {
        map.clear();
        return;
    }

    @Override
    public Set<K> keySet() {
        return map.keySet();
    }


    @Override
    @CacheInterceptor
    public CacheValue<V> put(K key, V value, long time, TimeUnit unit) {
        CacheValue<V> cacheValue = this.put(key, value);
        time = new Date().getTime() + unit.toMillis(time);
        cacheValue.setExpireTime(time);
        return cacheValue;
    }

    @Override
    @CacheInterceptor
    public CacheValue<V> put(K key, V value, Date expireTime) {
        CacheValue<V> cacheValue = this.put(key, value);
        cacheValue.setExpireTime(expireTime.getTime());
        return cacheValue;
    }

    @Override
    @CacheInterceptor
    public void expire(K key, long time, TimeUnit unit) {
        time = unit.toMillis(time);
        CacheValue<V> cacheValue = this.getCacheValue(key);
        if (Objects.isNull(cacheValue)){
            return;
        }
        cacheValue.setExpireTime(new Date().getTime() + time);
        return;
    }

    @Override
    @CacheInterceptor
    public void expireAt(K key, Date expireTime) {
        CacheValue<V> cacheValue = this.getCacheValue(key);
        if (Objects.isNull(cacheValue)){
            return;
        }
        cacheValue.setExpireTime(expireTime.getTime());
    }

    @Override
    public boolean checkExpire(K key, CacheValue<V> cacheValue) {
        long expireTime = cacheValue.getExpireTime();
        long now = new Date().getTime();
        // 如果元素有过期时间并且已经过期
        if (expireTime > 0 && expireTime < now){
            // 删除元素
            this.remove(key, new CacheRemoveContext<K,V>(key, simpleGet(key).getValue(), RemoveType.EXPIRE_REMOVE));
            return false;
        }
        return true;
    }

    @Override
    public void setSaveStrategy(IRDBSaveStrategy<K,V> saveStrategy, String fileName){
        this.rdbSaveStrategy = saveStrategy;
        this.rdbFileName = fileName;
        saveStrategy.startStrategy(this, this.rdbFileName);
    }

    @Override
    public void close() {
        rdbSaveStrategy.close(this, this.rdbFileName);
        aofSaveStrategy.close();
        System.out.println("缓存成功关闭");
    }

    @Override
    public Set<Map.Entry<K, CacheValue<V>>> entrySet() {
        return map.entrySet();
    }

    @Override
    public Map<K,CacheValue<V>> map() {
        return this.map;
    }

    /**
     * 读取rdb持久化文件
     * @param fileName
     * @since 0.0.3
     */
    @Override
    @CacheInterceptor
    public void readRdbFile(String fileName){
        File file = new File(fileName);
        if (!file.exists()){
            return;
        }
        this.map = rdbSaveStrategy.readSave(file, this.map);
    }

    @Override
    public void readAofFile(String fileName){
        File file = new File(fileName);
        if (!file.exists()){
            return;
        }
        aofSaveStrategy.readSave(file, this);
    }

    @Override
    public void setAofSaveStrategy(IAOFSaveStrategy aofSaveStrategy){
        this.aofSaveStrategy = aofSaveStrategy;
    }
}
